/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 abstract_stationary_adv_object.cpp

	$Header: /heroes4/abstract_adv_object.cpp $

	$NoKeywords: $

 ************************************************************************/

#include "precompiled.h"
#pragma warning( disable: 4786 ) // Disable the ever so helpful "identifier was truncated to '255' characters" warning

#include "abstract_stationary_adv_object.h"

#include <limits>
#include <stdexcept>
#include <stdlib.h>

#include "abstract_adv_object_visitor.h"
#include "adv_object_flag.h"
#include "adv_object_model.h"
#include "adv_object_model_cache.h"
#include "adv_object_type.h"
#include "adventure_tile_size.h"
#include "debug_switch.h"
#include "owned_ptr.h"
#include "player_color.h"
#include "screen_point.h"
#include "string_insensitive_compare.h"
#include "strip_resource_name.h"

using namespace abstract_stationary_adv_object_details;

// ------------------------------------------------------------------------------
// Unnamed namespace
// ------------------------------------------------------------------------------

namespace
{

	// ------------------------------------------------------------------------------
	// ------------------------------------------------------------------------------
	typedef t_adv_object_flag t_flag_subimage;

	// ------------------------------------------------------------------------------
	// ------------------------------------------------------------------------------
	t_uint32 pick_time_offset( int frames_per_second, int num_frames )
	{
		return rand() % ( ( 1000 / frames_per_second ) * num_frames );
	}

	// ------------------------------------------------------------------------------
	// ------------------------------------------------------------------------------
	std::auto_ptr< t_flag_subimage > create_flag_subimage(
		t_abstract_stationary_adv_object::t_model const &	model,
		t_player_color										color )
	{
		if ( !model.has_flag() || color == k_player_gray )
			return std::auto_ptr< t_flag_subimage >();

		t_screen_point flag_offset = model.get_flag_offset();
		t_screen_rect flag_rect = t_adv_object_flag::get_flag_extent() + flag_offset;

		bool overlaps_overlays = false;
		int depth_offset = std::numeric_limits< int >::max();

		t_adv_object_image const & image = model.get_image();
		int subimage_count = image.get_subimage_count();
		int subimage_num;
		for ( subimage_num = 0; subimage_num < subimage_count; ++subimage_num )
		{
			t_adv_object_subimage const & subimage = image.get_subimage( subimage_num );
			if ( subimage.is_underlay() || !intersect( flag_rect, subimage.get_rect() ) )
				continue;

			overlaps_overlays = true;
			int subimage_depth_offset = subimage.get_depth_offset();
			if ( subimage_depth_offset < depth_offset )
				depth_offset = subimage_depth_offset;
		}

		if ( !overlaps_overlays )
			depth_offset = -flag_offset.y / ( k_adventure_tile_height / 2 );

		return std::auto_ptr< t_flag_subimage >( new t_flag_subimage( color, flag_offset, depth_offset ) );
	}

} // Unnamed namespace

// ------------------------------------------------------------------------------
// Details namespace
// ------------------------------------------------------------------------------

namespace abstract_stationary_adv_object_details
{

	// ------------------------------------------------------------------------------
	// t_impl class
	// ------------------------------------------------------------------------------

	class t_impl
	{
	public:
		// Types
		typedef t_abstract_stationary_adv_object	t_outer;
		typedef t_outer::t_model					t_model;
		typedef t_outer::t_model_cache				t_model_cache;
		typedef t_outer::t_model_ptr				t_model_ptr;
		typedef t_adv_object_subimage				t_subimage;
		typedef t_owned_ptr< t_flag_subimage >		t_flag_subimage_ptr;

		// Constructors
		t_impl();
		t_impl( t_impl const & other );
		
		// Data members
		t_model_ptr			m_model_ptr;
		int					m_subimage_count;
		std::string			m_model_name;
		t_uint32			m_time_offset;
		t_player_color		m_flag_color;
		t_flag_subimage_ptr	m_flag_subimage_ptr;

		// Member functions
		int					compute_frame( t_uint32 time ) const;
		t_subimage const &	get_subimage( int subimage_num ) const;
		t_subimage const &	get_subimage(
								int			subimage_num,
								t_uint32	time,
								int	&		frame_num ) const;
		int					get_subimage_count() const;
		bool				has_flag_subimage() const;
		void				set_model( t_model_cache const& cache );

		// Operator
		t_impl & operator=( t_impl const & other );
	};

	// ------------------------------------------------------------------------------
	// t_impl members
	// ------------------------------------------------------------------------------

	// ------------------------------------------------------------------------------
	// ------------------------------------------------------------------------------
	t_impl::t_impl()
		:	m_subimage_count( 0 ),
			m_time_offset( 0 ),
			m_flag_color( k_player_gray )
	{
	}

	// ------------------------------------------------------------------------------
	// ------------------------------------------------------------------------------
	t_impl::t_impl( t_impl const & other )
		:	m_model_ptr( other.m_model_ptr ),
			m_subimage_count( other.m_subimage_count ),
			m_model_name( other.m_model_name ),
			m_time_offset( other.m_time_offset ),
			m_flag_color( other.m_flag_color )
	{
		if ( other.m_flag_subimage_ptr.get() != 0 )
			m_flag_subimage_ptr.reset( new t_flag_subimage( *other.m_flag_subimage_ptr ) );
	}

	// ------------------------------------------------------------------------------
	// ------------------------------------------------------------------------------
	int t_impl::compute_frame( t_uint32 time ) const
	{
		t_model const & model = *m_model_ptr;
		int frame_count = model.get_image().get_frame_count();
		return ( ( time + m_time_offset ) * model.get_frames_per_second() / 1000 ) % frame_count;
	}

	// ------------------------------------------------------------------------------
	// ------------------------------------------------------------------------------
	t_impl::t_subimage const & t_impl::get_subimage( int subimage_num ) const
	{
		if ( m_flag_subimage_ptr.get() != 0 && subimage_num == m_subimage_count )
			return *m_flag_subimage_ptr;

		return m_model_ptr->get_image().get_subimage( subimage_num );
	}

	// ------------------------------------------------------------------------------
	// ------------------------------------------------------------------------------
	t_impl::t_subimage const & t_impl::get_subimage(
		int			subimage_num,
		t_uint32	time,
		int &		frame_num ) const
	{
		if ( m_flag_subimage_ptr.get() != 0 && subimage_num == m_subimage_count )
		{
			frame_num = m_flag_subimage_ptr->compute_frame( time );
			return *m_flag_subimage_ptr;
		}

		frame_num = compute_frame( time );
		return m_model_ptr->get_image().get_subimage( subimage_num );
	}

	// ------------------------------------------------------------------------------
	// ------------------------------------------------------------------------------
	inline int t_impl::get_subimage_count() const
	{
		return m_subimage_count + ( m_flag_subimage_ptr.get() != 0 );
	}

	// ------------------------------------------------------------------------------
	// ------------------------------------------------------------------------------
	void t_impl::set_model( t_model_cache const& cache )
	{
		t_model_ptr new_model_ptr = cache.get();
	
		assert( new_model_ptr.get() != 0 );

		t_flag_subimage_ptr new_flag_subimage_ptr =
			create_flag_subimage( *new_model_ptr, m_flag_color );

		m_model_name = cache.get_name();
		m_model_ptr = new_model_ptr;
		m_subimage_count = m_model_ptr->get_image().get_subimage_count();
		m_time_offset =	pick_time_offset(
							new_model_ptr->get_frames_per_second(), 
							new_model_ptr->get_image().get_frame_count() );
		m_flag_subimage_ptr.swap( new_flag_subimage_ptr );
	}

	// ------------------------------------------------------------------------------
	// ------------------------------------------------------------------------------
	t_impl & t_impl::operator=( t_impl const & other )
	{
		t_owned_ptr< t_flag_subimage > new_flag_subimage_ptr;
		if ( other.m_flag_subimage_ptr.get() != 0 )
			new_flag_subimage_ptr.reset( new t_flag_subimage( *other.m_flag_subimage_ptr ) );

		m_model_ptr = other.m_model_ptr;
		m_subimage_count = other.m_subimage_count;
		m_model_name = other.m_model_name;
		m_time_offset = other.m_time_offset;
		m_flag_color = other.m_flag_color;
		m_flag_subimage_ptr.swap( new_flag_subimage_ptr );

		return *this;
	}

} // Details namespace

// ------------------------------------------------------------------------------
// t_abstract_stationary_adv_object class members
// ------------------------------------------------------------------------------

#pragma warning( push )
#pragma warning( disable: 4355 )
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_abstract_stationary_adv_object::t_abstract_stationary_adv_object(
	t_abstract_stationary_adv_object const & other )
	:	m_impl_ptr( other.m_impl_ptr )
{
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_abstract_stationary_adv_object::t_abstract_stationary_adv_object(
	std::string const & model_name )
{
	set_model( model_name );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_abstract_stationary_adv_object::t_abstract_stationary_adv_object( t_model_cache const& cache )
{
	set_model( cache );
}
#pragma warning( pop )

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_abstract_stationary_adv_object::~t_abstract_stationary_adv_object()
{
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_abstract_stationary_adv_object::accept( t_visitor & visitor )
{
	visitor.visit( *this );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_abstract_stationary_adv_object::accept( t_visitor & visitor ) const
{
	visitor.visit( *this );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
bool t_abstract_stationary_adv_object::animates() const
{
	t_impl const & impl = *m_impl_ptr;
	return		impl.m_model_ptr->get_image().get_frame_count() > 1
			||	(	impl.m_flag_subimage_ptr.get() != 0
				&&	impl.m_flag_subimage_ptr->get_frame_count() > 1 );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_abstract_stationary_adv_object::draw_shadow_to(
	t_uint32				current_time,
	t_screen_rect const &	source_rect,
	t_abstract_bitmap16 &	dest,
	t_screen_point const &	dest_point ) const
{
	t_impl const & impl = *m_impl_ptr;
	impl.m_model_ptr->get_image().draw_shadow_to(
		impl.compute_frame( current_time ),
		source_rect,
		dest,
		dest_point );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_abstract_stationary_adv_object::draw_shadow_to(
	t_uint32				current_time,
	t_abstract_bitmap16 &	dest,
	t_screen_point const &	dest_point ) const
{
	t_impl const & impl = *m_impl_ptr;
	impl.m_model_ptr->get_image().draw_shadow_to(
		impl.compute_frame( current_time ),
		dest,
		dest_point );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_abstract_stationary_adv_object::draw_subimage_to(
	int						subimage_num,
	t_uint32				current_time,
	t_screen_rect const &	source_rect,
	t_abstract_bitmap16 &	dest,
	t_screen_point const &	dest_point,
	int						alpha ) const
{
	int frame_num;
	t_adv_object_subimage const & subimage =
		m_impl_ptr->get_subimage( subimage_num, current_time, frame_num );
	subimage.draw_to( frame_num, source_rect, dest, dest_point );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_abstract_stationary_adv_object::draw_subimage_to(
	int						subimage_num,
	t_uint32				current_time,
	t_abstract_bitmap16 &	dest,
	t_screen_point const &	dest_point ) const
{
	int frame_num;
	t_adv_object_subimage const & subimage =
		m_impl_ptr->get_subimage( subimage_num, current_time, frame_num );
	subimage.draw_to( frame_num, dest, dest_point );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_abstract_stationary_adv_object::draw_to(
	t_uint32				current_time,
	t_screen_rect const &	source_rect,
	t_abstract_bitmap16 &	dest,
	t_screen_point const &	dest_point ) const
{
	t_impl const & impl = *m_impl_ptr;
	impl.m_model_ptr->get_image().draw_to(
		impl.compute_frame( current_time ),
		source_rect,
		dest,
		dest_point );

	if ( impl.m_flag_subimage_ptr.get() != 0 )
	{
		impl.m_flag_subimage_ptr->draw_to(
			0,
			source_rect,
			dest,
			dest_point );
	}
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_abstract_stationary_adv_object::draw_to(
	t_uint32				current_time,
	t_abstract_bitmap16 &	dest,
	t_screen_point const &	dest_point ) const
{
	t_impl const & impl = *m_impl_ptr;
	impl.m_model_ptr->get_image().draw_to(
		impl.compute_frame( current_time ),
		dest,
		dest_point );

	if ( impl.m_flag_subimage_ptr.get() != 0 )
	{
		impl.m_flag_subimage_ptr->draw_to(
			0,
			dest,
			dest_point );
	}
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_footprint const & t_abstract_stationary_adv_object::get_footprint() const
{
	return get_model();
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_abstract_stationary_adv_object::t_model const &
t_abstract_stationary_adv_object::get_model() const
{
	return *m_impl_ptr->m_model_ptr;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
static std::string const k_prefix = t_resource_traits< t_adv_object_model_24 >::prefix;
static std::string const k_extension = ".h4d";

std::string t_abstract_stationary_adv_object::get_model_name() const
{
	std::string result = m_impl_ptr->m_model_name;

	assert( strnicmp( result.c_str(), k_prefix.c_str(), k_prefix.size() ) == 0 );
	result = strip_prefix( result );
	result = strip_extension( result );
	return result;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_screen_rect t_abstract_stationary_adv_object::get_rect() const
{
	t_impl const & impl = *m_impl_ptr;

	t_screen_rect result = impl.m_model_ptr->get_image().get_rect();
	if ( impl.m_flag_subimage_ptr.get() != 0 )
		result = get_extent( result, impl.m_flag_subimage_ptr->get_rect() );

	return result;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_screen_rect t_abstract_stationary_adv_object::get_rect(
	t_uint32	current_time ) const
{
	t_impl const & impl = *m_impl_ptr;

	t_screen_rect result = impl.m_model_ptr->get_image().get_rect( impl.compute_frame( current_time ) );
	if ( impl.m_flag_subimage_ptr.get() != 0 )
	{
		t_flag_subimage const & flag_subimage = *impl.m_flag_subimage_ptr;
		result = get_extent( result, flag_subimage.get_rect( flag_subimage.compute_frame( current_time ) ) );
	}

	return result;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_screen_rect t_abstract_stationary_adv_object::get_shadow_rect() const
{
	return m_impl_ptr->m_model_ptr->get_image().get_shadow_rect();
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_screen_rect t_abstract_stationary_adv_object::get_shadow_rect(
	t_uint32	current_time ) const
{
	t_impl const & impl = *m_impl_ptr;
	return impl.m_model_ptr->get_image().get_shadow_rect( impl.compute_frame( current_time ) );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
int t_abstract_stationary_adv_object::get_subimage_count() const
{
	t_impl const & impl = *m_impl_ptr;
	return impl.m_subimage_count + ( impl.m_flag_subimage_ptr.get() != 0 );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
int t_abstract_stationary_adv_object::get_subimage_depth_offset(
	int	subimage_num ) const
{
	return m_impl_ptr->get_subimage( subimage_num ).get_depth_offset();
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_screen_rect t_abstract_stationary_adv_object::get_subimage_rect(
	int	subimage_num ) const
{
	return m_impl_ptr->get_subimage( subimage_num ).get_rect();
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_screen_rect t_abstract_stationary_adv_object::get_subimage_rect(
	int			subimage_num,
	t_uint32	time ) const
{
	int frame_num;
	t_impl::t_subimage const & subimage = m_impl_ptr->get_subimage( subimage_num, time, frame_num );
	return subimage.get_rect( frame_num );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
bool t_abstract_stationary_adv_object::hit_test(
	t_uint32				current_time,
	t_screen_point const &	point ) const
{
	t_impl const & impl = *m_impl_ptr;
	if ( impl.m_model_ptr->get_image().hit_test( impl.compute_frame( current_time ), point ) )
		return true;

	if ( impl.m_flag_subimage_ptr.get() != 0 )
	{
		t_flag_subimage const & flag_subimage = *impl.m_flag_subimage_ptr;
		return flag_subimage.hit_test( flag_subimage.compute_frame( current_time ), point );
	}
	
	return false;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
bool t_abstract_stationary_adv_object::is_decorative() const
{
	return get_model().get_qualified_type().get() == k_adv_object_decorative;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
bool t_abstract_stationary_adv_object::needs_redrawing(
	t_uint32	last_update_time,
	t_uint32	current_time ) const
{
	t_impl const & impl = *m_impl_ptr;
	return impl.compute_frame( current_time ) != impl.compute_frame( last_update_time );
}

// ------------------------------------------------------------------------------
// set the model for the object based on a model name.  Model name does not include
// prefix and suffix
// ------------------------------------------------------------------------------
void t_abstract_stationary_adv_object::set_model( std::string const & new_model_name )
{
	assert( strnicmp( new_model_name.c_str(), k_prefix.c_str(), k_prefix.size() ) != 0 );
	m_impl_ptr->set_model( get_adv_object_model_cache( new_model_name ) );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_abstract_stationary_adv_object::set_model( t_model_cache const& cache )
{
	m_impl_ptr->set_model( cache );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
bool t_abstract_stationary_adv_object::subimage_animates(
	int	subimage_num ) const
{
	t_impl const & impl = *m_impl_ptr;
	assert( subimage_num >= 0 && subimage_num < impl.get_subimage_count() );

	// If the subimage number is the same as the subimage count then its the
	// flag subimage.
	if ( subimage_num == impl.m_subimage_count )
	{
		assert( impl.m_flag_subimage_ptr.get() != 0 );
		return impl.m_flag_subimage_ptr->get_frame_count() > 1;
	}

	return impl.m_model_ptr->get_image().get_frame_count() > 1;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
bool t_abstract_stationary_adv_object::subimage_is_underlay(
	int	subimage_num ) const
{
	return m_impl_ptr->get_subimage( subimage_num ).is_underlay();
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
bool t_abstract_stationary_adv_object::subimage_needs_redrawing(
	int			subimage_num,
	t_uint32	last_update_time,
	t_uint32	current_time ) const
{
	t_impl const & impl = *m_impl_ptr;
	assert( subimage_num >= 0 && subimage_num < impl.get_subimage_count() );

	// If the subimage number is the same as the subimage count then its the
	// flag subimage.
	if ( subimage_num == impl.m_subimage_count )
	{
		assert( impl.m_flag_subimage_ptr.get() != 0 );
		t_flag_subimage const & flag_subimage = *impl.m_flag_subimage_ptr;
		return		flag_subimage.compute_frame( current_time )
				!=	flag_subimage.compute_frame( last_update_time );
	}

	return		impl.compute_frame( current_time )
			!=	impl.compute_frame( last_update_time );
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
t_abstract_stationary_adv_object & t_abstract_stationary_adv_object::operator=(
	t_abstract_stationary_adv_object const & other )
{
	m_impl_ptr = other.m_impl_ptr;
	return *this;
}

// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
void t_abstract_stationary_adv_object::set_flag_color( t_player_color new_flag_color )
{
	assert( new_flag_color >= 0 && new_flag_color < k_player_color_count );

	if ( new_flag_color == m_impl_ptr.get_const()->m_flag_color )
		return;

	t_impl & impl = *m_impl_ptr;
	if ( impl.m_flag_subimage_ptr.get() == 0 )
		impl.m_flag_subimage_ptr = create_flag_subimage( *impl.m_model_ptr, new_flag_color );
	else
	{
		assert( impl.m_model_ptr->has_flag() );
		if ( new_flag_color != k_player_gray )
			impl.m_flag_subimage_ptr->set_color( new_flag_color );
		else
			impl.m_flag_subimage_ptr.reset();
	}
	impl.m_flag_color = new_flag_color;
}
